C++

C++入门(二)

Posted by alonealice on 2021-02-22

类的创建

1
Student liLei;  //创建对象

除了创建单个对象,还可以创建对象数组:

1
Student allStu[100];

在类体中声明函数,而将函数定义放在类体外面:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student{
public:
//成员变量
char *name;
int age;
float score;
//成员函数
void say(); //函数声明
};
//函数定义
void Student::say(){
cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
}

使用对象指针

上面代码中创建的对象 stu 在栈上分配内存,需要使用&获取它的地址,例如:

1
2
Student stu;
Student *pStu = &stu;

也可以在堆上创建对象,这个时候就需要使用前面讲到的new关键字,例如:

1
Student *pStu = new Student;

栈内存是程序自动管理的,不能使用 delete 删除在栈上创建的对象;堆内存由程序员管理,对象使用完毕后可以通过 delete 删除。

有了对象指针后,可以通过箭头->来访问对象的成员变量和成员函数,这和通过结构体指针来访问它的成员类似,请看下面的示例:

1
2
3
4
pStu -> name = "小明";
pStu -> age = 15;
pStu -> score = 92.5f;
pStu -> say();

类对象的内存模型

  • 全局数据区(data area) 静态数据和常量
  • 代码区(code area) 所有类成员函数和非成员函数代码
  • 栈区(stack area)
  • 堆区(heap area)(即自由存储区) 对于非静态成员变量,我们是在类的实例化过程中(构造对象)才在栈区或者堆区为其分配内存,是为每个对象生成一个拷贝,所以它是属于对象的。

构造函数的参数初始化表

构造函数的一项重要功能是对成员变量进行初始化,为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用参数初始化表。

1
2
3
4
//采用参数初始化表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
//TODO:
}

m_name(name), m_age(age), m_score(score)语句,这个语句的意思相当于函数体内部的m_name = name; m_age = age; m_score = score;语句,也是赋值的意思。

使用参数初始化表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简明明了。

参数初始化表可以用于全部成员变量,也可以只用于部分成员变量。

1
2
3
4
Student::Student(char *name, int age, float score): m_name(name){
m_age = age;
m_score = score;
}

参数初始化表还有一个很重要的作用,那就是初始化 const 成员变量。初始化 const 成员变量的唯一方法就是使用参数初始化表

1
2
3
4
5
6
7
8
9
10
11
class VLA{
private:
const int m_len;
int *m_arr;
public:
VLA(int len);
};
//必须使用参数初始化表来初始化 m_len
VLA::VLA(int len): m_len(len){
m_arr = new int[len];
}

析构函数

析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。

1
2
Demo::Demo(string s): m_s(s){ }
Demo::~Demo(){ cout<<m_s<<endl; }

在函数内部创建的对象是局部对象,它和局部变量类似,位于栈区,函数执行结束时会调用这些对象的析构函数。

new 创建的对象位于堆区,通过 delete 删除时才会调用析构函数;如果没有 delete,析构函数就不会被执行。

this指针

this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。

this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student{
public:
void setname(char *name);
void setage(int age);
void setscore(float score);
void show();
private:
char *name;
int age;
float score;
};
void Student::setname(char *name){
this->name = name;
}

注意,this 是一个指针,要用->来访问成员变量或成员函数。

static静态成员变量和函数

static 成员变量必须在类声明的外部初始化,具体形式为:

type class::name = value;

如:int Student::m_total = 0;

**static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。**即便是在main方法或者其他方法中使用时,都必须要是在类外初始化过的变量。

static 成员变量既可以通过对象来访问,也可以通过类来访问。请看下面的例子:

1
2
3
4
5
6
7
8
//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
Student stu("小明", 15, 92.5f);
stu.m_total = 20;
//通过对象指针来访问 static 成员变量
Student *pstu = new Student("李华", 16, 96);
pstu -> m_total = 20;

这三种方式是等效的。

和静态成员变量类似,静态成员函数在声明时要加 static,在定义时不能加 static。

友元函数和友元类

将非成员函数声明为友元函数

借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;
class Student{
public:
Student(char *name, int age, float score);
public:
friend void show(Student *pstu); //将show()声明为友元函数
private:
char *m_name;
int m_age;
float m_score;
};
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
//非成员函数
void show(Student *pstu){
cout<<pstu->m_name<<"的年龄是 "<<pstu->m_age<<",成绩是 "<<pstu->m_score<<endl;
}
int main(){
Student stu("小明", 15, 90.6);
show(&stu); //调用友元函数
Student *pstu = new Student("李磊", 16, 80.5);
show(pstu); //调用友元函数
return 0;
}

将其他类的成员函数声明为友元函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Address;  //提前声明Address类
//声明Student类
class Student{
public:
Student(char *name, int age, float score);
public:
void show(Address *addr);
private:
char *m_name;
int m_age;
float m_score;
};
//声明Address类
class Address{
private:
char *m_province; //省份
char *m_city; //城市
char *m_district; //区(市区)
public:
Address(char *province, char *city, char *district);
//将Student类中的成员函数show()声明为友元函数
friend void Student::show(Address *addr);
};
//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){
cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"区"<<endl;
}
//实现Address类
Address::Address(char *province, char *city, char *district){
m_province = province;
m_city = city;
m_district = district;
}

可以将其他类的成员函数申明为友元函数,那么这个函数可以使用自己类的成员变量。

友元类

不仅可以将一个函数声明为一个类的“朋友”,还可以将整个类声明为另一个类的“朋友”,这就是友元类。友元类中的所有成员函数都是另外一个类的友元函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//声明Student类
class Student{
public:
Student(char *name, int age, float score);
public:
void show(Address *addr);
private:
char *m_name;
int m_age;
float m_score;
};
//声明Address类
class Address{
public:
Address(char *province, char *city, char *district);
public:
//将Student类声明为Address类的友元类
friend class Student;
private:
char *m_province; //省份
char *m_city; //城市
char *m_district; //区(市区)
};
//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){
cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"区"<<endl;
}
//实现Address类
Address::Address(char *province, char *city, char *district){
m_province = province;
m_city = city;
m_district = district;
}
int main(){
Student stu("小明", 16, 95.5f);
Address addr("陕西", "西安", "雁塔");
stu.show(&addr);

Student *pstu = new Student("李磊", 16, 80.5);
Address *paddr = new Address("河北", "衡水", "桃城");
pstu -> show(paddr);
return 0;
}

class和struct的区别

C++中的 struct 和 class 基本是通用的,唯有几个细节不同:

  • 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
  • class 继承默认是 private 继承,而 struct 继承默认是 public 继承(《C++继承与派生》一章会讲解继承)。
  • class 可以使用模板,而 struct 不能

强烈建议使用 class 来定义类,而使用 struct 来定义结构体,这样做语义更加明确

string类

string 是 C++ 中常用的一个类,使用 string 类需要包含头文件<string>

初始化方式:

1
2
3
4
string s1;  //默认为""
string s2 = "c plus plus";
string s3 = s2;
string s4 (5, 's'); //"sssss"

访问字符串中的某个字符,可以使用s[i]这种方式

转换函数 c_str(),该函数能够将 string 字符串转换为C风格的字符串(const char*),c语言的字符串的真实长度,是长度 +1,而c++下不是。

有了 string 类,我们可以使用++=运算符来直接拼接字符串

1
2
3
4
5
6
7
8
9
string s1 = "first ";
string s2 = "second ";
char *s3 = "third ";
char s4[] = "fourth ";
char ch = '@';
string s5 = s1 + s2;
string s6 = s1 + s3;
string s7 = s1 + s4;
string s8 = s1 + ch;

其他方法

插入字符串:string& insert (size_t pos, const string& str);

删除字符串:string& erase (size_t pos = 0, size_t len = npos);

提取子字符串:string substr (size_t pos = 0, size_t len = npos) const;

字符串查找:

size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;

第一个参数为待查找的子字符串,它可以是 string 字符串,也可以是C风格的字符串。第二个参数为开始查找的位置(下标);如果不指明,则从第0个字符开始查找。

rfind() 函数:

rfind() 和 find() 很类似,同样是在字符串中查找子字符串,不同的是 find() 函数从第二个参数开始往后查找,而 rfind() 函数则最多查找到第二个参数处,如果到了第二个参数所指定的下标还没有找到子字符串,则返回一个无穷大值4294967295。

find_first_of() 函数:find_first_of() 函数用于查找子字符串和字符串共同具有的字符在字符串中首次出现的位置

1
2
3
string s1 = "first second second third";
string s2 = "asecond";
int index = s1.find_first_of(s2); // index = 3,共同字符为s

引用的概念与使用

引用的定义方式类似于指针,只是用&取代了*,语法格式为:

1
type &name = data;
1
2
3
4
5
6
7
int main(){
int a = 99;
int &b = a;
cout<<a<<", "<<b<<endl; //输出99, 99
cout<<&a<<", "<<&b<<endl; //输出 0x28ff44, 0x28ff44
return 0;
}

注意,引用在定义时需要添加&,在使用时不能添加&,使用时添加&表示取地址。

引用作为参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//直接传递参数内容
void swap1(int a, int b){
int temp = a;
a = b;
b = temp;
}
//传递指针
void swap2(int *p1, int *p2){
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
//按引用传参
void swap3(int &a, int &b){
int temp = a;
a = b;
b = temp;
}

int main(){
int num1 = 10, num2 = 20;
swap1(num1, num2);
swap2(&num1, &num2);
swap3(num1, num2);
//结果一样
return 0;
}

引用作为函数返回值

1
2
3
4
5
6
7
8
9
10
int &plus10(int &n){
n = n + 10;
return n;
}
int main(){
int num1 = 10;
int num2 = plus10(num1); // num2 = 20
int &num3 = plus10(num1); //不同编译器下结果不同
return 0;
}